﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SampleEquityPairStrategyPlugin.cs" company="Open Trader">
//   Copyright (c) David Denis (david.denis@systemathics.com)
// </copyright>
// <summary>
//   |  Open Trader - The Open Source Systematic Trading Platform
//   |
//   |  This program is free software: you can redistribute it and/or modify
//   |  it under the terms of the GNU General Public License as published by
//   |  the Free Software Foundation, either version 2 of the License, or
//   |  (at your option) any later version.
//   |
//   |  This program is distributed in the hope that it will be useful,
//   |  but WITHOUT ANY WARRANTY; without even the implied warranty of
//   |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//   |  GNU General Public License for more details.
//   |
//   |  You should have received a copy of the GNU General Public License
//   |  along with this program.  If not, see http://www.gnu.org/licenses
//   |
//   |  Up to date informations about Open Trader can be found at :
//   |    http://opentrader.org
//   |    http://opentrader.codeplex.com
//   |
//   |  For professional services, please visit us at :
//   |    http://www.systemathics.com
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Org.OpenTrader.Framework.Forge.Plugins
{
    #region Using Directives

    using System;

    using Org.OpenTrader.Framework.Forge.Enums;
    using Org.OpenTrader.Framework.Forge.Events;
    using Org.OpenTrader.Framework.Forge.Interfaces;
    using Org.OpenTrader.Framework.LiveObjects;
    using Org.OpenTrader.Framework.LiveObjects.Interfaces;

    #endregion

    /// <summary>
    /// The sample pair strategy plugin.
    /// </summary>
    [Credits("Sample Strategy Plugin", "Example to illustrate the use of strategies", "David Denis", 
        "Copyright (C) David Denis <mailto:david.denis@systemathics.com>")]
    [ImplementsInterface("Org.OpenTrader.Framework.Forge.Interfaces.IStrategyPlugin")]
    [ConfigurationField("Asset1Code", typeof(ConfigurationField.String), "First asset of the pair", IsMandatory = true, DefaultValue = "IBM US Equity")]
    [ConfigurationField("Asset2Code", typeof(ConfigurationField.String), "Second asset of the pair", IsMandatory = true, DefaultValue = "DELL US Equity")]
    [ConfigurationField("TradingScope", typeof(ConfigurationField.Enum), typeof(ETradingScope), "Scope", IsMandatory = true, 
        DefaultValue = ETradingScope.Virtual)]
    [ConfigurationField("Weights", typeof(ConfigurationField.Table), "Weights of each asset", IsMandatory = true)]
    [ConfigurationField("Documentation", typeof(ConfigurationField.File), "Just kidding", IsMandatory = true)]
    ////[ConfigurationField("SampleMandatoryFile", typeof(ConfigurationField.File), "SampleMandatoryFile", IsMandatory = true)]
        ////[ConfigurationField("SampleOptionalFile", typeof(ConfigurationField.File), "SampleOptionalFile")]   
        ////[ConfigurationField("SampleMandatoryTable", typeof(ConfigurationField.Table), "SampleMandatoryTable", IsMandatory = true)]
        ////[ConfigurationField("SampleOptionalTable", typeof(ConfigurationField.Table), "SampleOptionalTable")]
        ////[ConfigurationField("SampleMandatoryInt", typeof(ConfigurationField.Int), "SampleMandatoryInt", IsMandatory = true, DefaultValue = 15)]
        ////[ConfigurationField("SampleOptionalInt", typeof(ConfigurationField.Int), "SampleOptionalInt")]
        ////[ConfigurationField("SampleMandatoryDecimal", typeof(ConfigurationField.Decimal), "SampleMandatoryDecimal", IsMandatory = true, DefaultValue = 15.0)]
        ////[ConfigurationField("SampleOptionalDecimal", typeof(ConfigurationField.Decimal), "SampleOptionalDecimal")]
        ////[ConfigurationField("SampleMandatoryBool", typeof(ConfigurationField.Bool), "SampleMandatoryBool", IsMandatory = true, DefaultValue = true)]
        ////[ConfigurationField("SampleOptionalBool", typeof(ConfigurationField.Bool), "SampleOptionalBool")]
        ////[ConfigurationField("SampleMandatoryEnum", typeof(ConfigurationField.Enum), typeof(ETradingScope), "SampleMandatoryEnum", IsMandatory = true, DefaultValue = ETradingScope.Virtual)]
        ////[ConfigurationField("SampleOptionalEnum", typeof(ConfigurationField.Enum), typeof(ETradingScope), "SampleOptionalEnum")]   
    public sealed class SampleEquityPairStrategyPlugin : StrategyPlugin
    {
        private static readonly Identity Identity = Identity.Create(System.Reflection.MethodBase.GetCurrentMethod());

        #region Constants and Fields

        /// <summary>
        /// The asset 1.
        /// </summary>
        private IInstrument asset1;

        /// <summary>
        /// The asset 2.
        /// </summary>
        private IInstrument asset2;

        /// <summary>
        /// The bestAsk.
        /// </summary>
        private double bestAsk;

        /// <summary>
        /// The bestBid.
        /// </summary>
        private double bestBid;

        /// <summary>
        /// The qtyAsset 1.
        /// </summary>
        private int qtyAsset1 = 10;

        /// <summary>
        /// The qtyAsset 2.
        /// </summary>
        private int qtyAsset2 = 6;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="SampleEquityPairStrategyPlugin"/> class. 
        /// </summary>
        /// <param name="descriptor">
        /// The descriptor.
        /// </param>
        /// <param name="container">
        /// The container.
        /// </param>
        public SampleEquityPairStrategyPlugin(ILiveObjectDescriptor descriptor, ILiveObjectContainer container)
            : base(descriptor, container)
        {
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets Name.
        /// </summary>
        public override string Name
        {
            get
            {
                return string.Format("Sample Pair Strategy (\"{0}\",\"{1}\")", this.Configuration["Asset1Code"], this.Configuration["Asset2Code"]);
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// The startup impl.
        /// </summary>
        /// <returns>
        /// </returns>
        public override Status Create()
        {
            //// Testing the "table" support
            ////if (false)
            ////{
            ////    Table weights = this.Configuration["Weights"];
            ////    int v1 = weights[0,6];
            ////    double v2 = weights[0,10];
            ////    Table v3 = weights[0,15];
            ////}

            // Trading scope of instruments
            var scope = ConfigurationField.Enum.As<ETradingScope>(this.Configuration["TradingScope"]);

            // Lookup instrument 1 using (code, scope) pair
            var lookupstatusasset1 = this.Referential.CreateInstrument((string)this.Configuration["Asset1Code"], scope);
            if (!lookupstatusasset1)
            {
                return Status.Failure(Identity, "Create: Wasn't able to lookup asset1", lookupstatusasset1);
            }

            // Here it is
            this.asset1 = lookupstatusasset1.NestedObject;

            // Lookup instrument 2 using (code, scope) pair
            var lookupstatusasset2 = this.Referential.CreateInstrument((string)this.Configuration["Asset2Code"], scope);
            if (!lookupstatusasset2)
            {
                return Status.Failure(Identity, "Create: Wasn't able to lookup asset2", lookupstatusasset2);
            }

            // Here it is
            this.asset2 = lookupstatusasset2.NestedObject;

            try
            {
                // Subscribe asset 1 to feed 
                this.asset1.FeedEventCB += this.OnFeedAsset1;

                // Subscribe asset 2 to feed 
                this.asset2.FeedEventCB += this.OnFeedAsset2;

                // Subscribe asset 1 to tracking 
                this.asset1.OrderTrackingEventCB += this.OnOrderTrackingAsset1;

                // Subscribe asset 2 to tracking 
                this.asset2.OrderTrackingEventCB += this.OnOrderTrackingAsset2;
            }
            catch (Exception e)
            {
                return Status.Failure(Identity, "Create: Some subscription failed, got exception " + e.ToString());
            }

            return Status.Success(Identity);
        }

        /// <summary>
        /// The shutdown impl.
        /// </summary>
        /// <returns>
        /// </returns>
        public override Status Destroy()
        {
            try
            {
                // Unsubscribe asset 1 to feed 
                this.asset1.FeedEventCB -= this.OnFeedAsset1;

                // Unsubscribe asset 2 to feed 
                this.asset2.FeedEventCB -= this.OnFeedAsset2;

                // Unsubscribe asset 1 to feed 
                this.asset1.OrderTrackingEventCB -= this.OnOrderTrackingAsset1;

                // Unsubscribe asset 2 to feed 
                this.asset2.OrderTrackingEventCB -= this.OnOrderTrackingAsset2;
            }
            catch (Exception e)
            {
                return Status.Failure(Identity, "Destroy: Some subscription failed, got exception " + e.ToString());
            }

            // Done
            return Status.Success(Identity);
        }

        #endregion

        #region Methods

        /// <summary>
        /// The contribute ask.
        /// </summary>
        /// <param name="asset1Bid">
        /// The asset 1Bid.
        /// </param>
        /// <param name="asset1Ask">
        /// The asset 1Ask.
        /// </param>
        /// <param name="asset2Bid">
        /// The asset 2Bid.
        /// </param>
        /// <param name="asset2Ask">
        /// The asset 2Ask.
        /// </param>
        private void ContributeAsk(double asset1Bid, double asset1Ask, double asset2Bid, double asset2Ask)
        {
            Stdout.WriteLine("ContributeAsk");
            var ordersAsset1 = this.asset1.Buy(EOrderType.Limit, asset1Ask, this.qtyAsset1, this.Configuration["BookingPath"]);
            var ordersAsset2 = this.asset2.Sell(EOrderType.Limit, asset2Bid, this.qtyAsset2, this.Configuration["BookingPath"]);
        }

        /// <summary>
        /// The contribute bid.
        /// </summary>
        /// <param name="asset1Bid">
        /// The asset 1Bid.
        /// </param>
        /// <param name="asset1Ask">
        /// The asset 1Ask.
        /// </param>
        /// <param name="asset2Bid">
        /// The asset 2Bid.
        /// </param>
        /// <param name="asset2Ask">
        /// The asset 2Ask.
        /// </param>
        private void ContributeBid(double asset1Bid, double asset1Ask, double asset2Bid, double asset2Ask)
        {
            Stdout.WriteLine("ContributeBid");
            var ordersAsset1 = this.asset1.Sell(EOrderType.Limit, asset1Bid, this.qtyAsset1, this.Configuration["BookingPath"]);
            var ordersAsset2 = this.asset2.Buy(EOrderType.Limit, asset2Ask, this.qtyAsset2, this.Configuration["BookingPath"]);
        }

        /// <summary>
        /// Don't ask me what this does :-) It's for the sake of example
        /// </summary>
        /// <param name="x">
        /// parameter x
        /// </param>
        /// <param name="y">
        /// parameter y
        /// </param>
        /// <param name="z">
        /// parameter z
        /// </param>
        /// <returns>
        /// The my calculation.
        /// </returns>
        private double MyCalculation(double? x, double? y, double? z)
        {
            if (!x.HasValue || !y.HasValue || !z.HasValue)
            {
                return 0;
            }

            return (x.Value - y.Value) / z.Value;
        }

        /// <summary>
        /// Feed events for asset 1
        /// </summary>
        /// <param name="sender">
        /// The sender of event
        /// </param>
        /// <param name="args">
        /// The arguments
        /// </param>
        private void OnFeedAsset1(object sender, FeedEventArgs args)
        {
            if (args is FeedEventArgs.ConnectionStatus)
            {
            }
            else if (args is FeedEventArgs.MarketBookUpdate)
            {
                if (this.asset1.MarketBook.Bids[0] == null || this.asset1.MarketBook.Asks[0] == null)
                {
                    return;
                }

                this.UpdateSomethingThatNeedsMarketBook(
                    this.asset1.MarketBook.Bids[0].Price, 
                    this.asset1.MarketBook.Asks[0].Price, 
                    this.asset2.MarketBook.Bids[0].Price, 
                    this.asset2.MarketBook.Asks[0].Price);
            }
            else if (args is FeedEventArgs.MarketFieldUpdate)
            {
                this.UpdateSomethingThatNeedMarketFields();
            }
        }

        /// <summary>
        /// Feed events for asset 2
        /// </summary>
        /// <param name="sender">
        /// The sender of event
        /// </param>
        /// <param name="args">
        /// The arguments
        /// </param>
        private void OnFeedAsset2(object sender, FeedEventArgs args)
        {
            if (args is FeedEventArgs.ConnectionStatus)
            {
            }

            if (args is FeedEventArgs.MarketBookUpdate)
            {
                if (this.asset1.MarketBook.Bids[0] == null || this.asset1.MarketBook.Asks[0] == null)
                {
                    return;
                }

                this.UpdateSomethingThatNeedsMarketBook(
                    this.asset1.MarketBook.Bids[0].Price, 
                    this.asset1.MarketBook.Asks[0].Price, 
                    this.asset2.MarketBook.Bids[0].Price, 
                    this.asset2.MarketBook.Asks[0].Price);
            }
            else if (args is FeedEventArgs.MarketFieldUpdate)
            {
                this.UpdateSomethingThatNeedMarketFields();
            }
        }

        /// <summary>
        /// The on order tracking asset 1.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="args">
        /// The args.
        /// </param>
        private void OnOrderTrackingAsset1(object sender, OrderTrackingEventArgs args)
        {
            // Stdout.WriteLine("{0} {1}", PrettyPrinter.Timings.Format(Timings.Now), args.ToString());
            var instrument = sender as IInstrument;

            if (args is OrderTrackingEventArgs.AsynchronousUpdate)
            {
                // We have access here to the tracking cell and to the OrderEventArgs.OrderEventArgs that triggered this changes
                var asyncargs = args as OrderTrackingEventArgs.AsynchronousUpdate;

                // Do something with smme cell status
                if (asyncargs.OrderTrackingCell.Open)
                {
                    // do something
                    if (Rand.NextGaussian() > 1)
                    {
                        var result = instrument.Cancel(args.ObjectID);
                    }
                }

                // Do something with related order status event args if needed
                if (asyncargs.RelatedOrderEventArgs is OrderEventArgs.Status)
                {
                    // Some status changed
                }

                // Do something with related order execution event args if needed
                if (asyncargs.RelatedOrderEventArgs is OrderEventArgs.Execution)
                {
                    // Some execution was received
                    // do something
                    if (Rand.NextGaussian() > 1)
                    {
                        var result = instrument.Cancel(args.ObjectID);
                    }
                }
            }
            else if (args is OrderTrackingEventArgs.SynchronousUpdate)
            {
                // We have access there to the tracking cell only
                var syncargs = args as OrderTrackingEventArgs.SynchronousUpdate;
            }
        }

        /// <summary>
        /// The on order tracking asset 2.
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="args">
        /// The args.
        /// </param>
        private void OnOrderTrackingAsset2(object sender, OrderTrackingEventArgs args)
        {
        }

        /// <summary>
        /// Returns true if we should contribute on Ask side
        /// </summary>
        /// <param name="basketAsk">
        /// best ask of the basket
        /// </param>
        /// <returns>
        /// The should contribute ask.
        /// </returns>
        private bool ShouldContributeAsk(double basketAsk)
        {
            // Here we will use two kind of thing :
            // - The indicator that allows us to be willing to contribute
            // - The current exposure we got on the strategy (using this.Exposure.* and/or _[asset1|asset2].Exposure)
            var g = Rand.NextGaussian();
            if (g > 2)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Returns true if we should contribute on Bid side
        /// </summary>
        /// <param name="basketBid">
        /// best bid of the basket
        /// </param>
        /// <returns>
        /// The should contribute bid.
        /// </returns>
        private bool ShouldContributeBid(double basketBid)
        {
            // Here we will use two kind of thing :
            // - The indicator that allows us to be willing to contribute
            // - The current exposure we got on the strategy (using this.Exposure.* and/or _[asset1|asset2].Exposure)
            var g = Rand.NextGaussian();
            if (g > 2)
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Update something that needs a market field, for example some calculation using VWAP, HI,LO
        /// </summary>
        private void UpdateSomethingThatNeedMarketFields()
        {
            IInstrument i;
            i = this.asset1;

            if (i.MarketFields[EMarketField.High] == null || i.MarketFields[EMarketField.Low] == null || i.MarketFields[EMarketField.VWap] == null)
            {
                return;
            }

            var a = this.MyCalculation(i.MarketFields[EMarketField.High], i.MarketFields[EMarketField.Low], i.MarketFields[EMarketField.VWap]);

            i = this.asset2;
            if (i.MarketFields[EMarketField.High] == null || i.MarketFields[EMarketField.Low] == null || i.MarketFields[EMarketField.VWap] == null)
            {
                return;
            }

            var b = this.MyCalculation(i.MarketFields[EMarketField.High], i.MarketFields[EMarketField.Low], i.MarketFields[EMarketField.VWap]);
        }

        /// <summary>
        /// Update something that needs a market book, for example a basket
        /// </summary>
        /// <param name="asset1Bid">
        /// best bid asset 1
        /// </param>
        /// <param name="asset1Ask">
        /// best ask asset 1
        /// </param>
        /// <param name="asset2Bid">
        /// best bid asset 2
        /// </param>
        /// <param name="asset2Ask">
        /// best ask asset 2
        /// </param>
        private void UpdateSomethingThatNeedsMarketBook(double asset1Bid, double asset1Ask, double asset2Bid, double asset2Ask)
        {
            if (double.IsNaN(asset1Bid) || double.IsNaN(asset1Ask))
            {
                return;
            }

            if (double.IsNaN(asset2Bid) || double.IsNaN(asset2Ask))
            {
                return;
            }

            this.bestAsk = -this.asset1.MarketBook.Asks[0].Price + this.asset2.MarketBook.Bids[0].Price;
            this.bestBid = +this.asset1.MarketBook.Bids[0].Price - this.asset2.MarketBook.Asks[0].Price;
            if (this.ShouldContributeAsk(this.bestAsk))
            {
                this.ContributeAsk(asset1Bid, asset1Ask, asset2Bid, asset2Ask);
            }

            if (this.ShouldContributeBid(this.bestBid))
            {
                this.ContributeBid(asset1Bid, asset1Ask, asset2Bid, asset2Ask);
            }
        }

        #endregion
    }
}